package com.bblocks.common.anti_reptile.data;

import com.bblocks.common.anti_reptile.config.AntiReptileProperties;
import com.bblocks.common.anti_reptile.config.bean.RuleComm;
import com.bblocks.common.anti_reptile.constant.AntiReptileConsts;
import com.bblocks.common.anti_reptile.constant.RuleTypeEnum;
import com.bblocks.common.anti_reptile.module.IpUserNumVo;
import com.bblocks.common.anti_reptile.module.RuleStatVO;
import com.bblocks.common.anti_reptile.module.RuleVO;
import com.bblocks.common.anti_reptile.rule.RuleData;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalField;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * <p>Project: bb-anti-reptile - RuleDataFromDb</p>
 * <p>描述：提取ip/user访问规则-简单版本平均/最高qps
 * 1、系统未提取到数据，走默认规则
 * 2、默认规则未配置 则无规则
 * 3、ip 用户数-未提取到按1计算
 *
 * 4、保存数据（2大类）
 * </p>
 *
 * @Author Zhao [125043150@qq.com]
 * @Date 2024/3/25 10:36
 * @Version 1.0
 * @since 8
 */
@ConditionalOnClass({ RedissonClient.class, JdbcTemplate.class})
@Slf4j
//@Component
public class RuleDataFromDb implements RuleData {
    /**
     * 分页
     */
    private static final String pageSql = " LIMIT ? OFFSET ?";//offset从0开始
    /**
     * url访问记录汇总
     */
    private static final String sql_url_sum = "select * from sys_access_sum where `day` is null";
    /**
     * ip汇总统计
     */
    private static final String sql_ip_sum = "select * from sys_access_ip_sum where user_num>1";
    private static final LocalCachedMapOptions localCacheOptions = LocalCachedMapOptions.defaults()
            // 用于淘汰清除本地缓存内的元素
            // 共有以下几种选择:
            // LFU - 统计元素的使用频率，淘汰用得最少（最不常用）的。
            // LRU - 按元素使用时间排序比较，淘汰最早（最久远）的。
            // SOFT - 元素用Java的WeakReference来保存，缓存元素通过GC过程清除。
            // WEAK - 元素用Java的SoftReference来保存, 缓存元素通过GC过程清除。
            // NONE - 永不淘汰清除缓存元素。
            .evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LRU)
            // 如果缓存容量值为0表示不限制本地缓存容量大小
            .cacheSize(1000)
            // 以下选项适用于断线原因造成了未收到本地缓存更新消息的情况。
            // 断线重连的策略有以下几种：
            // CLEAR - 如果断线一段时间以后则在重新建立连接以后清空本地缓存
            // LOAD - 在服务端保存一份10分钟的作废日志
            //        如果10分钟内重新建立连接，则按照作废日志内的记录清空本地缓存的元素
            //        如果断线时间超过了这个时间，则将清空本地缓存中所有的内容
            // NONE - 默认值。断线重连时不做处理。
            .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.CLEAR)
            // 以下选项适用于不同本地缓存之间相互保持同步的情况
            // 缓存同步策略有以下几种：
            // INVALIDATE - 默认值。当本地缓存映射的某条元素发生变动时，同时驱逐所有相同本地缓存映射内的该元素
            // UPDATE - 当本地缓存映射的某条元素发生变动时，同时更新所有相同本地缓存映射内的该元素
            // NONE - 不做任何同步处理
            .syncStrategy(LocalCachedMapOptions.SyncStrategy.INVALIDATE)
            // 每个Map本地缓存里元素的有效时间，默认毫秒为单位--10分钟
            .timeToLive(600, TimeUnit.SECONDS)
            // 每个Map本地缓存里元素的最长闲置时间，默认毫秒为单位--1分钟
            .maxIdle(60, TimeUnit.SECONDS);

    @Resource
    private AntiReptileProperties antiReptileProperties;
    @Lazy
    @Resource
    private RedissonClient redissonClient;

    @Autowired
    private JdbcTemplate jdbcTemplate;
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    /**
     * url频率数据Map
     */
    private  RLocalCachedMap<String, RuleStatVO> urlRates ;
    /**
     * ip用户量Map
     */
    private RLocalCachedMap<String, Integer> ipUsers;

    @PostConstruct
    public void init() {
        urlRates = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + URL_RATE,localCacheOptions);
        ipUsers = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + IP_USERS,localCacheOptions);
        //启动线程，提取并计算数据
        log.info("启动线程定时同步数据(来源：db)！");
        //初始执行提取数据
        scheduledExecutorService.schedule(new TimerTask() {
            @Override
            public void run() {
                try{
                    allDatas(LocalDate.now());
                }catch (Exception e){
                    log.error("提取数据失败：",e);
                }
            }
        }, 5_000, TimeUnit.MILLISECONDS);

        //启动定时器
        scheduledExecutorService.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try{
                    allDatas(LocalDate.now());
                }catch (Exception e){
                    log.error("提取数据失败：",e);
                }
            }
        }, getRunStartTimeSeconds(this.antiReptileProperties), 24*60*60,TimeUnit.SECONDS);
    }

    @PreDestroy
    public void destroy() {
        scheduledExecutorService.shutdownNow();
    }


    @Override
    public RuleVO theRule(@NonNull RuleTypeEnum ruleTypeEnum,@NonNull String url, String ip) {
        RuleComm rule = antiReptileProperties.ruleByType(ruleTypeEnum);
        if(rule == null){
            return null;
        }

        RuleStatVO statVO = urlRates.get(url);
        if(statVO == null){//使用默认值
            Integer defaultQps = rule.defaultQps();
            if(defaultQps == null){
                return null;
            }
            RuleVO result = RuleVO
                    .builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg(defaultQps)
                    .rateMax( defaultQps )
                    .build();
            return result;
        }else {
            Integer ratio = 1;
            if(ruleTypeEnum == RuleTypeEnum.IP_PLUS){
                ratio = ipUsers.get(ip);
                if(ratio == null || ratio <= 0)
                    ratio = 1;
            }
            RuleVO result = RuleVO
                    .builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg((ruleTypeEnum==RuleTypeEnum.USER ? statVO.getRateAvgUser() : statVO.getRateAvgIp()) * ratio)
                    .rateMax( (int)(rule.getMultiplier()/100.0 * (ruleTypeEnum==RuleTypeEnum.USER ? statVO.getRateMaxUser() : statVO.getRateMaxIp()) * ratio))
                    .build();
            return result;
        }
    }

    /**
     * 覆盖值，忽略空值
     * @param ruleStatVOParam
     */
    @Override
    public void saveRule(@NonNull RuleStatVO ruleStatVOParam) {
        RuleStatVO ruleStatVO = urlRates.get(ruleStatVOParam.getUrl());
        if(ruleStatVO == null){
            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVOParam);
        }else{//把非空的值覆盖

            if(ruleStatVOParam.getRateAvgIp() != null){
                ruleStatVO.setRateAvgIp(ruleStatVOParam.getRateAvgIp());
            }
            if(ruleStatVOParam.getRateMaxIp() != null){
                ruleStatVO.setRateMaxIp(ruleStatVOParam.getRateMaxIp());
            }

            if(ruleStatVOParam.getRateAvgUser() != null){
                ruleStatVO.setRateAvgUser(ruleStatVOParam.getRateAvgUser());
            }
            if(ruleStatVOParam.getRateMaxUser() != null){
                ruleStatVO.setRateMaxUser(ruleStatVOParam.getRateMaxUser());
            }
            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVO);
        }
    }

    /**
     *
     * @param userNoVo
     */
    @Override
    public void saveIpUser(@NonNull IpUserNumVo userNoVo) {
        if(userNoVo.getUserNum() !=null && userNoVo.getUserNum()>1) {//1以及以下的无意义
            ipUsers.put(userNoVo.getIp(), userNoVo.getUserNum());
        }
    }



    private void allDatas(@NonNull LocalDate now) {
        RLock lock = redissonClient.getLock(AntiReptileConsts.BB_ANTI_PRE + RuleData.SYNC_LOCK);
        boolean lockResult = false;
        try {
            lockResult = lock.tryLock(1, 60 * 60, TimeUnit.SECONDS);//一个小时把
            if (lockResult) {
                RBucket<String> bucket = redissonClient.getBucket(AntiReptileConsts.BB_ANTI_PRE + RuleData.SYNC_DAY);
                String theDay = bucket.get();
                String day = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                if( theDay != null && theDay.equals(day) ){
                    log.info("同步数据日：{} 已同步,本次不执行！",day);
                    return;
                }
                log.info("提取流量控制数据(来源：db)！");
                int pageSize = 100;
                String urlSql = sql_url_sum + pageSql;

                int total = 0;
                int pageNum = 1;
                while (true) {
                    List<RuleStatVO> urlStatVOs = jdbcTemplate.query(urlSql, new BeanPropertyRowMapper<>(RuleStatVO.class),pageSize, (pageNum-1)*pageSize);
                    if (!urlStatVOs.isEmpty()) {
                        urlStatVOs.forEach(vo -> {
                            this.saveRule(vo);
                        });
                        pageNum++;
                        total += urlStatVOs.size();
                    } else
                        break;
                }
                log.info("共提取到url统计数据:{}", total);
                total = 0;
                pageNum = 1;
                String ipSql = sql_ip_sum + pageSql;
                while (true) {
                    List<IpUserNumVo> ipStatVOs = jdbcTemplate.query(ipSql, new BeanPropertyRowMapper<>(IpUserNumVo.class), pageSize, (pageNum-1)*pageSize);
                    if (!ipStatVOs.isEmpty()) {
                        ipStatVOs.forEach(vo -> {
                            this.saveIpUser(vo);
                        });
                        pageNum++;
                        total += ipStatVOs.size();
                    } else
                        break;
                }
                log.info("共提取到ip统计数据:{}", total);
                //设置执行日志
                bucket.set(day);
            }
        }catch (Exception e){
            log.error("同步异常数据：",e);
        }finally {
            if(lockResult)
                lock.unlock();//释放
        }
    }

    /**
     * 获取运行（明天一点）
     * @return
     */
    private LocalDateTime getRunStartTime(AntiReptileProperties antiReptileProperties){
        return LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(antiReptileProperties.getSyncTime()!=null?antiReptileProperties.getSyncTime() : 1 ,0,0));
    }

    private long getRunStartTimeSeconds(AntiReptileProperties antiReptileProperties){
        LocalDateTime nextTime = getRunStartTime(antiReptileProperties);
        Duration duration = Duration.between(LocalDateTime.now(),nextTime); // 后面减去前面

        return duration.getSeconds();
    }

}
